package judesart.app.client.ui.admin.user;

import java.util.ArrayList;
import java.util.List;

import judesart.app.client.controller.UserController;
import judesart.app.client.controller.UserControllerAsync;
import judesart.app.client.error.ErrorUtil;
import judesart.app.client.model.UserModel;
import judesart.app.client.resources.Resources;
import judesart.app.domain.Role;
import judesart.app.domain.User;
import judesart.app.shared.ValidationException;

import com.allen_sauer.gwt.log.client.Log;
import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style.HorizontalAlignment;
import com.extjs.gxt.ui.client.Style.IconAlign;
import com.extjs.gxt.ui.client.Style.Orientation;
import com.extjs.gxt.ui.client.Style.SelectionMode;
import com.extjs.gxt.ui.client.binding.FormBinding;
import com.extjs.gxt.ui.client.data.BasePagingLoader;
import com.extjs.gxt.ui.client.data.BeanModel;
import com.extjs.gxt.ui.client.data.PagingLoadResult;
import com.extjs.gxt.ui.client.data.PagingLoader;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
import com.extjs.gxt.ui.client.event.SelectionEvent;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.store.Store;
import com.extjs.gxt.ui.client.store.StoreEvent;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.Info;
import com.extjs.gxt.ui.client.widget.Label;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.VerticalPanel;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.button.ButtonBar;
import com.extjs.gxt.ui.client.widget.form.CheckBox;
import com.extjs.gxt.ui.client.widget.form.CheckBoxGroup;
import com.extjs.gxt.ui.client.widget.form.Field;
import com.extjs.gxt.ui.client.widget.form.FormButtonBinding;
import com.extjs.gxt.ui.client.widget.form.FormPanel;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.layout.FlowLayout;
import com.extjs.gxt.ui.client.widget.layout.MarginData;
import com.extjs.gxt.ui.client.widget.layout.RowData;
import com.extjs.gxt.ui.client.widget.layout.RowLayout;
import com.extjs.gxt.ui.client.widget.tips.ToolTip;
import com.extjs.gxt.ui.client.widget.tips.ToolTipConfig;
import com.extjs.gxt.ui.client.widget.toolbar.PagingToolBar;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.rpc.AsyncCallback;

public class UserPanel extends LayoutContainer
{
    private Button cancelButton;
    private Button deleteButton;
    private FormBinding formBindings;
    private FormPanel formPanel;
    public Grid<UserModel> grid;
    private boolean isDirty = false;
    private PagingLoader<PagingLoadResult<UserModel>> loader;
    private UserLoaderProxy proxy;
    private Button saveButton;
    private ListStore<UserModel> store;
    private ToolTip tooltip;
    UserControllerAsync userController = GWT.create(UserController.class);
    private CheckBoxGroup rolesCheckBoxGroup;
    private FormButtonBinding buttonBinding;
    private ContentPanel contentPanel;
    public Button addNew;

    private FormPanel createForm()
    {
        final FormPanel formPanel = new FormPanel();
        formPanel.setHeaderVisible(false);
        formPanel.setStyleAttribute("padding", "0");
        formPanel.setStyleAttribute("margin", "0");

        final TextField<String> name = new TextField<String>();
        name.setName("name");
        name.setFieldLabel("Name");
        name.setAllowBlank(false);
        name.setAutoWidth(true);
        formPanel.add(name);

        final TextField<String> username = new TextField<String>();
        username.setName("username");
        username.setFieldLabel("UserName");
        username.setWidth(10);
        username.setAllowBlank(false);
        formPanel.add(username);

        final TextField<String> password = new TextField<String>();
        password.setName("password");
        password.setFieldLabel("Password");
        password.setPassword(true);
        password.setWidth(10);
        password.setAllowBlank(false);
        formPanel.add(password);

        final TextField<String> passwordAgain = new TextField<String>();
        passwordAgain.setName("passwordAgain");
        passwordAgain.setFieldLabel("Pass Again");
        passwordAgain.setPassword(true);
        passwordAgain.setWidth(10);
        passwordAgain.setAllowBlank(false);
        passwordAgain.addListener(Events.Blur, new Listener<FieldEvent>()
        {
            @Override
            public void handleEvent(FieldEvent be)
            {
                validatePasswordAgain(password, passwordAgain);
            }
        });
        formPanel.add(passwordAgain);

        final TextField<String> email = new TextField<String>();
        email.setName("email");
        email.setFieldLabel("Email");
        email.setAllowBlank(false);
        formPanel.add(email);

        Label rolesLabel = new Label("Roles");
        formPanel.add(rolesLabel);
        final VerticalPanel rolesPanel = new VerticalPanel();
        rolesPanel.setBorders(false);
        for (Role role : Role.values())
        {
            CheckBox roleCB = new CheckBox();
            roleCB.setName(role.name());
            roleCB.setBoxLabel(role.label());
            roleCB.setFieldLabel(null);
            rolesPanel.add(roleCB);
        }
        formPanel.add(rolesPanel, new MarginData(0, 0, 0, 100));
        final CheckBox featured = new CheckBox();
        featured.setName("featured");
        featured.setFieldLabel("Featured Artist");
        formPanel.add(featured, new MarginData(0, 0, 0, 100));

        saveButton = new Button("Save");
        saveButton.addListener(Events.OnClick, new Listener<ButtonEvent>()
        {
            @Override
            public void handleEvent(ButtonEvent be)
            {
                if (!validatePasswordAgain(password, passwordAgain))
                {
                    be.cancelBubble();
                    return;
                }
                UserModel model = (UserModel) formBindings.getModel();
                if (model == null)
                {
                    User user = new User();
                    user.setUsername(username.getValue());
                    user.setName(name.getValue());
                    user.setPassword(password.getValue());
                    user.setEmail(email.getValue());
                    for (Component comp : rolesPanel.getItems())
                    {
                        CheckBox cb = (CheckBox) comp;
                        if (cb.getValue())
                        {
                            Role role = Role.valueOf(cb.getName());
                            user.addRole(role);
                        }
                    }
                    user.setFeatured(featured.getValue());
                    createUser(user);
                } else
                {
                    updateUser(model.getBean());
                }
                formPanel.setEnabled(false);
            }
        });
        formPanel.addButton(saveButton);
        deleteButton = new Button("Delete");
        deleteButton.addListener(Events.Select, new Listener<ButtonEvent>()
        {
            @Override
            public void handleEvent(ButtonEvent be)
            {
                UserModel model = (UserModel) formBindings.getModel();

                if (model == null)
                {
                    refresh(formPanel);
                } else
                {
                    deleteUser(model.getBean());
                }
            }
        });
        formPanel.addButton(deleteButton);
        cancelButton = new Button("Cancel");
        cancelButton.addListener(Events.OnClick, new Listener<ButtonEvent>()
        {
            @Override
            public void handleEvent(ButtonEvent be)
            {
                refresh(formPanel);
            }
        });
        formPanel.addButton(cancelButton);

        formPanel.setButtonAlign(HorizontalAlignment.CENTER);

        FormButtonBinding binding = new FormButtonBinding(formPanel);
        binding.addButton(saveButton);

        return formPanel;
    }

    private Grid<UserModel> createGrid(
            final PagingLoader<PagingLoadResult<UserModel>> loader,
            ListStore<UserModel> store)
    {
        List<ColumnConfig> columns = new ArrayList<ColumnConfig>();
        columns.add(new ColumnConfig("username", "UserName", 100));
        columns.add(new ColumnConfig("name", "Name", 200));
        columns.add(new ColumnConfig("roles", "Roles", 100));

        ColumnModel cm = new ColumnModel(columns);

        final Grid<UserModel> grid = new Grid<UserModel>(store, cm);
        grid.setStateId("usersGrid");
        grid.setStateful(true);
        grid.addListener(Events.Attach, new Listener<GridEvent<BeanModel>>()
        {
            public void handleEvent(GridEvent<BeanModel> be)
            {
                loader.load(0, 500);
            }
        });
        grid.setLoadMask(true);
        grid.setBorders(true);
        grid.setAutoExpandColumn("name");
        return grid;
    }

    private void createUser(User user)
    {
        Log.debug("create user");
        userController.createUser(user, new AsyncCallback<User>()
        {

            @Override
            public void onFailure(Throwable caught)
            {
                Info.display("Error", "Unable to create user : {0}",
                        caught.getMessage());
                if (caught instanceof ValidationException)
                {
                    String message = ErrorUtil.showFormErrors(formPanel,
                            (ValidationException) caught);
                    formPanel.setEnabled(true);
                    showTooltip(message, true);
                }
            }

            @Override
            public void onSuccess(User result)
            {
                Info.display("User Created.",
                        "User {0} was successfully created.", result.getName());
                formPanel.setVisible(false);
                formPanel.setEnabled(true);
                formBindings.unbind();
                loader.load();
                isDirty = false;
            }
        });
    }

    private void deleteUser(final User user)
    {
        Log.debug("delete user");
        userController.deleteUser(user.getId(), new AsyncCallback<Void>()
        {
            @Override
            public void onFailure(Throwable caught)
            {
                Info.display("Error", "Unable to delete user : {0}",
                        caught.getMessage());
                loader.load();
                isDirty = false;
            }

            @Override
            public void onSuccess(Void result)
            {
                Info.display("User Deleted.",
                        "User {0} was successfully deleted.", user.getName());
                formPanel.setVisible(false);
                formPanel.setEnabled(true);
                formBindings.unbind();
                loader.load();
                isDirty = false;
            }
        });
    }

    protected void hideTooltip()
    {
        if (tooltip != null)
        {
            tooltip.hide();
            tooltip.disable();
        }
    }

    @Override
    protected void onRender(Element parent, int index)
    {
        super.onRender(parent, index);
        setStyleAttribute("margin", "0");

        Log.debug("starting ... ");

        contentPanel = new ContentPanel();

        contentPanel.setHeading("Users");
        contentPanel.setFrame(true);
        contentPanel.setSize(800, 400);
        contentPanel.setLayout(new RowLayout(Orientation.HORIZONTAL));

        proxy = new UserLoaderProxy(userController);

        FlowLayout layout = new FlowLayout(0);
        setLayout(layout);

        loader = new BasePagingLoader<PagingLoadResult<UserModel>>(proxy);

        loader.setRemoteSort(true);

        store = new ListStore<UserModel>(loader);
        store.setMonitorChanges(true);

        store.addListener(Store.Update, new Listener<StoreEvent<UserModel>>()
        {
            @Override
            public void handleEvent(StoreEvent<UserModel> be)
            {
                isDirty = true;
            }
        });

        final PagingToolBar toolBar = new PagingToolBar(50);
        toolBar.bind(loader);

        formPanel = createForm();
        formPanel.setVisible(false);
        formBindings = new FormBinding(formPanel, true);
        buttonBinding = new FormButtonBinding(formPanel);
        buttonBinding.addButton(saveButton);

        grid = createGrid(loader, store);
        grid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
        grid.getSelectionModel().addListener(Events.BeforeSelect,
                new Listener<SelectionEvent<UserModel>>()
                {
                    @Override
                    public void handleEvent(SelectionEvent<UserModel> be)
                    {
                        if (formPanel.isVisible() && isDirty)
                        {
                            be.setCancelled(true);
                            showTooltip("You need to save or cancel your changes.");
                        }
                    }
                });
        grid.getSelectionModel().addListener(Events.SelectionChange,
                new Listener<SelectionChangedEvent<UserModel>>()
                {
                    public void handleEvent(SelectionChangedEvent<UserModel> be)
                    {
                        hideTooltip();
                        if (be.getSelection().size() > 0)
                        {
                            deleteButton.setEnabled(true);
                            formPanel.setVisible(true);
                            formBindings.bind(be.getSelection().get(0));
                        } else
                        {
                            formPanel.setVisible(false);
                            formBindings.unbind();
                        }
                    }
                });

        contentPanel.add(grid, new RowData(.595, 1));
        contentPanel.setBottomComponent(toolBar);

        contentPanel.add(formPanel, new RowData(.405, .8));

        addNew = new Button();
        addNew.setText("Add User");
        addNew.setIconAlign(IconAlign.RIGHT);
        addNew.setIcon(Resources.ICONS.user_add());
        addNew.addListener(Events.OnClick, new Listener<ButtonEvent>()
        {
            @Override
            public void handleEvent(ButtonEvent be)
            {
                formBindings.unbind();
                formPanel.clear();
                deleteButton.setEnabled(false);
                formPanel.setVisible(true);
                isDirty = true;
            }
        });

        ButtonBar bar = new ButtonBar();
        bar.setAlignment(HorizontalAlignment.RIGHT);
        bar.add(addNew);
        contentPanel.setTopComponent(bar);

        add(contentPanel);
    }

    private Field getField(FormPanel formPanel, String name)
    {
        for (Field field : formPanel.getFields())
        {
            if (field.getName().equals(name))
            {
                return field;
            }
        }
        return null;
    }

    private void refresh(final FormPanel panel)
    {
        panel.setVisible(false);
        formBindings.unbind();
        loader.load();
        isDirty = false;
    }

    protected void showTooltip(String msg)
    {
        showTooltip(msg, true);
    }

    protected void showTooltip(String msg, boolean autohide)
    {
        if (tooltip == null)
        {
            ToolTipConfig config = new ToolTipConfig();
            config.setAutoHide(true);
            config.setMouseOffset(new int[] { 0, 0 });
            config.setTitle(GXT.MESSAGES.rowEditor_tipTitleText());
            config.setAnchorToTarget(true);
            config.setAnchor("left");
            tooltip = new ToolTip(formPanel, config);
            tooltip.setMaxWidth(600);
        }
        ToolTipConfig config = tooltip.getToolTipConfig();
        config.setAutoHide(autohide);
        config.setText(msg);
        tooltip.update(config);
        tooltip.enable();
        tooltip.show();
    }

    private void updateUser(User user)
    {
        Log.debug("update user : user.getUserRoles().size() : "
                + user.getUserRoles().size());
        userController.updateUser(user, new AsyncCallback<User>()
        {
            @Override
            public void onFailure(Throwable caught)
            {
                Info.display("Error", "Unable to update user : {0}",
                        caught.getMessage());
                if (caught instanceof ValidationException)
                {
                    String message = ErrorUtil.showFormErrors(formPanel,
                            (ValidationException) caught);
                    formPanel.setEnabled(true);
                    showTooltip(message, true);
                }
            }

            @Override
            public void onSuccess(User result)
            {
                Info.display("User Updated.",
                        "User {0} was successfully updated.", result.getName());
                formPanel.setVisible(false);
                formPanel.setEnabled(true);
                formBindings.unbind();
                loader.load();
                isDirty = false;
            }
        });

    }

    private boolean validatePasswordAgain(final TextField<String> password,
            final TextField<String> passwordAgain)
    {
        if (password.isDirty())
        {
            if (!password.getValue().equals(passwordAgain.getValue()))
            {
                passwordAgain.forceInvalid("Passwords don't match.");
                return false;
            } else
            {
                passwordAgain.clearInvalid();
            }
        }
        return true;
    }

    @Override
    public void setVisible(boolean visible)
    {
        super.setVisible(visible);
        if (contentPanel != null)
        {
            contentPanel.setVisible(visible);
        }
    }

}
